﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Djs.Tools.Tresor.Data
{
    internal class TresorLock
    {
        #region Konstruktory, řízení načítání dat z klíče
        private TresorLock()
        {
            this.Key = null;
            this.Header = new byte[HEADER_LENGTH];
            this.Signature = new byte[SIGNATURE_LENGTH];
            this.Work = new byte[WORK_LENGTH];
            this.CharMap = new char[CHARMAP_LENGTH];
            this.Locks = new LockOne[LOCKS_LENGTH];

        }
        private TresorLock(TresorKey tKey)
            : this()
        {
            this.Key = tKey;
        }
        /// <summary>
        /// Obsah klíče fyzicky načtený ze souboru s klíčem
        /// </summary>
        private TresorKey Key;
        /// <summary>
        /// Načte do this všechna data z klíče.
        /// </summary>
        private void FillFromKey()
        {
            this.Key.Reset();
            this.LoadHeader();
            this.LoadCharMap();
            this.LoadLockOnes();
            long usedBytes = this.Key.Position;
        }
        #endregion
        #region Vytvoření zámku z klíče, ze souboru

        internal static TresorLock CreateFromFile(string fileName)
        {
            TresorKey tKey = TresorKey.CreateFromFile(fileName);
            return CreateFromKey(tKey);
        }
        /// <summary>
        /// Vrátí nový zámek /Lock) vytvořený z daného klíče
        /// </summary>
        /// <param name="tKey"></param>
        /// <returns></returns>
        internal static TresorLock CreateFromKey(TresorKey tKey)
        {
            TresorLock tLock = new TresorLock(tKey);
            tLock.FillFromKey();
            return tLock;
        }
        /// <summary>
        /// Odemkne tento zámek heslem (heslo aplikuje na klíč).
        /// </summary>
        /// <param name="password"></param>
        internal void UnlockWithPassword(string password)
        {
            this.Key.UnlockWithPassword(password);
            this.FillFromKey();
        }

        #endregion
        #region Header (=buffery Header, Signature, Work)
        private void LoadHeader()
        {
            this.Header = this.Key.ReadCodes(HEADER_LENGTH);
            this.Signature = this.Key.ReadCodes(SIGNATURE_LENGTH);
            this.Work = this.Key.ReadCodes(WORK_LENGTH);
        }
        private byte[] Header;
        private byte[] Signature;
        private byte[] Work;
        private const int HEADER_LENGTH = 16;
        private const int SIGNATURE_LENGTH = 48;
        private const int WORK_LENGTH = 53;
        #endregion
        #region CharMap (=buffer CharMap)
        /// <summary>
        /// Iniciace map z klíče
        /// </summary>
        private void LoadCharMap()
        {
            char[] charAll = TresorText.ALL_CODES.ToCharArray();
            this.CharMap = this.Key.ReadUniqueValues(CHARMAP_LENGTH, 5, ref charAll);
            this.TestCharMap();
        }
        private void TestCharMap()
        {
            byte[] input = this.Work;

            string crypt = null;
            using (System.IO.MemoryStream mr = new System.IO.MemoryStream(input))
            using (System.IO.StringWriter sw = new System.IO.StringWriter())
            {
                TresorText tt = new TresorText(this.CharMap);
                tt.Crypt(mr, sw);

                crypt = sw.GetStringBuilder().ToString();
            }

            byte[] output = null;
            using (System.IO.StringReader sr = new System.IO.StringReader(crypt))
            using (System.IO.MemoryStream mw = new System.IO.MemoryStream())
            {
                TresorText tt = new TresorText(this.CharMap);
                tt.Decrypt(sr, mw);

                output = mw.ToArray();
            }

            if (input.Length != output.Length)
                throw new InvalidOperationException("TestCharMap selhal, nesouhlasí délka dat před a po šifrování.");

            for (int i = 0; i < input.Length; i++)
            {
                if (input[i] != output[i])
                    throw new InvalidOperationException("TestCharMap selhal, nesouhlasí hodnota dat před a po šifrování.");
            }
        }
        /// <summary>
        /// Mapa pro převedení čísla 0 až 15 na jeden znak
        /// </summary>
        private char[] CharMap;
        private const int CHARMAP_LENGTH = 16;
        #endregion
        #region Locks (=pole LockOne), třídy LockOne a LockBit
        private void LoadLockOnes()
        {
            for (int i = 0; i < LOCKS_LENGTH; i++)
            {
                LockOne one = LockOne.CreateFromKey(i, this.Key);
                this.Locks[i] = one;
            }
        }
        /// <summary>
        /// Mapa jednotlivých zámků, které kryptují jeden znak v jedné z 128 variant, plus 128 void variant
        /// </summary>
        private LockOne[] Locks;
        private const int LOCKS_LENGTH = 256;
        #region sub classes LockOne, LockBit
        internal class LockOne
        {
            #region Konstrukce, načtení dat zámečku z klíče
            private LockOne()
            {
                this._Index = 0;
                this._Bits = new LockBit[256];
            }
            private LockOne(int index)
                : this()
            {
                this._Index = index;
            }
            private void FillFromKey(TresorKey key)
            {
                int[] targets = key.ReadUniqueValues(256, 8, CreateArray(0, 256));       // Vrátí pole hodnot int, každá hodnota je v rozsahu 8 bitů, každý prvek je unikátní, pořadí prvků je určeno na základě aktuálního obsahu klíče. Hodnoty určují přesouvání bitů.
                UInt32[] masks = key.ReadBitArray32(256, 1);                             // Vrátí pole hodnot UInt32, každá hodnota je v rozsahu 1 bitu (=0 nebo 1), hodnoty prvků jsou načteny z aktuálního obsahu klíče. Hodnoty určují maskování bitů při jejich převodu.
                byte[] reserved = key.ReadCodes(5);                // Rezerva na rozvoj uvnitř LockOne. Načítaná data musí mít i do budoucna shodnou délku, jinak se klíč stane nepoužitelný. Radši si rezervuju další 4 Byte (první používám na SizeMask):
                this._SizeMask = reserved[0];
                for (int s = 0; s < 256; s++)
                    this._Bits[s] = new LockBit(s, targets[s], masks[s]);
            }
            internal static LockOne CreateFromKey(int index, TresorKey key)
            {
                LockOne tOne = new LockOne(index);
                tOne.FillFromKey(key);
                return tOne;
            }
            /// <summary>
            /// Index tohoto zámečku
            /// </summary>
            internal int Index { get { return _Index; } } private int _Index;
            /// <summary>
            /// Předpisy pro Shuffle jednotlivých bitů.
            /// Každý záznam předepisuje shuffle pro jeden bit (plain, crypted).
            /// </summary>
            internal LockBit[] Bits { get { return _Bits; } } private LockBit[] _Bits;
            /// <summary>
            /// Maska posledního byte, který obsahuje počet byte v datech a obsahuje paritu čistých (plain) dat.
            /// </summary>
            internal byte SizeMask { get { return _SizeMask; } } private byte _SizeMask;
            #endregion
            #region Crypt
            /// <summary>
            /// Kryptuje daná data (plain) s pomocí this informací. 
            /// Vstupní data mohou mít velikost 0 až 32 Byte, jinak bude vyhozena chyba.
            /// Kryptovaná data mají vždy velikost 33 Byte.
            /// Na poslední pozici kryptovaných dat bude umístěn byte nesoucí informaci o délce plain dat + paritní bit plain dat.
            /// </summary>
            /// <param name="plain"></param>
            /// <param name="nextLockIndex"></param>
            /// <returns></returns>
            internal byte[] Crypt(byte[] plain)
            {
                return null;
            }
            #endregion
        }
        /// <summary>
        /// Popisuje Shuffle jednoho bitu odněkud někam
        /// </summary>
        internal class LockBit
        {
            internal LockBit(int plain, int crypted, UInt32 xor)
            {
                byte mask, index;

                Parse(plain, out index, out mask);
                this.PlainBitIndex = index;
                this.PlainBitMask = mask;

                Parse(crypted, out index, out mask);
                this.CryptedBitIndex = index;
                this.CryptedBitMask = mask;

                this.XorBit = (xor != 0);
            }
            public override string ToString()
            {
                string text = "(Plain[" + this.PlainBitIndex.ToString() + "] & 0x" + this.PlainBitMask.ToString("X2") + ") " + 
                              (this.XorBit ? "!= " : "== ") +
                              "(Crypted[" + this.CryptedBitIndex.ToString() + "] & 0x" + this.CryptedBitMask.ToString("X2") + ")";
                return text;
            }
            private static void Parse(int value, out byte index, out byte mask)
            {
                mask = (byte)Bits.GetMask321((byte)(value & 0x07));      // Dolní tři bity určují bit, z něj určíme masku bitu (0x01 - 0x02 - 0x04 - 0x08 - 0x10 - 0x20 - 0x40 - 0x80)
                index = (byte)((value & 0xF8) >> 3);                     // Horních 5 bitů je index 0 až 31
            }
            /// <summary>Index byte Plain (=čitelná data), 0 ÷ 31</summary>
            internal readonly byte PlainBitIndex;
            /// <summary>Maska bitu Plain (=čitelná data), 0x01 ÷ 0x80</summary>
            internal readonly byte PlainBitMask;
            /// <summary>Index byte Crypted (=zašifrovaná data), 0 ÷ 31</summary>
            internal readonly byte CryptedBitIndex;
            /// <summary>Maska bitu Crypted (=zašifrovaná data), 0x01 ÷ 0x80</summary>
            internal readonly byte CryptedBitMask;
            /// <summary>true pokud se bit při shuffle Xor-uje, false když ne</summary>
            internal readonly bool XorBit;
        }
        #endregion
        #endregion
        #region Internal static služby
        /// <summary>
        /// Vytvoří a vrátí nové pole dat obsahující hodnoty Int32 počínaje hodnotou "from" a konče hodnotu "to - 1" (hodnota "to" již není vložena do pole).
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        internal static int[] CreateArray(int from, int count)
        {
            int[] result = new int[count];
            int value = from;
            for (int i = 0; i < count; i++)
                result[i] = value++;
            return result;
        }
        #endregion
    }
}
